package com.gitee.yannzh.rune.extension.mp.query;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ClassUtils;
import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.ColumnCache;
import com.gitee.yannzh.rune.support.crud.annotation.Query;
import com.gitee.yannzh.rune.support.crud.query.Condition;
import com.gitee.yannzh.rune.support.crud.query.Operator;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Stream;

/**
 * @author Yann
 * @description mp动态查询构造帮助类
 * @date 2023-12-26
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class QueryHelper {


    public static <Q, E> QueryWrapper<E> build(Q query, Class<E> entityClass) {
        QueryWrapper<E> queryWrapper = Wrappers.query();
        // 没有查询条件，直接返回
        if (query == null) {
            return queryWrapper;
        }
        Class<?> clazz = ClassUtils.getUserClass(query.getClass());
        // 获取查询条件中所有的字段
//        PropertyDescriptor[] properties = ReflectUtils.getBeanProperties(clazz);
        Stream.of(clazz.getDeclaredFields()).forEach(field -> {
            buildQueryWrapper(queryWrapper, entityClass, query, field);
        });
        return queryWrapper;
    }

    private static <Q, E> void buildQueryWrapper(final QueryWrapper<E> queryWrapper, Class<?> entityClass, Q query, Field field) {
        assert field != null;
        boolean accessible = field.isAccessible();
        try {
            Query annotation = field.getDeclaredAnnotation(Query.class);
            ReflectionUtils.makeAccessible(field);
            Object value = ReflectionUtils.getField(field, query);
            boolean empty = false;
            Condition condition = Condition.EQ;
            Operator operator = Operator.AND;
            String fieldName = field.getName();
            if (annotation != null) {
                empty = annotation.empty();
                condition = annotation.condition();
                operator = annotation.operator();
                fieldName = StrUtil.blankToDefault(annotation.property(), fieldName);
            }
            if (StrUtil.isBlankIfStr(value) && !empty) {
                return;
            }

            if (queryWrapper.isNonEmptyOfWhere() && operator == Operator.OR) {
                queryWrapper.or();
            }

            //MP缓存去拿colum名
            Map<String, ColumnCache> columnMap = LambdaUtils.getColumnMap(entityClass);
            ColumnCache columnCache = columnMap.get(LambdaUtils.formatKey(fieldName));
            String colum = Objects.nonNull(columnCache) ? columnCache.getColumn() : fieldName;

            Class<?> propertyType = field.getType();

            Method wrapperMethod = null;
            switch (condition) {
                case IS_NULL:
                case IS_NOT_NULL:
                    wrapperMethod = queryWrapper.getClass().getMethod(condition.getName(), String.class);
                    wrapperMethod.invoke(queryWrapper, colum);
                    break;
                case BETWEEN:
                case NOT_BETWEEN:
                    if (!(propertyType.isArray() || Collection.class.isAssignableFrom(propertyType))) {
                        throw new RuntimeException("范围查询参数必须是集合或数组");
                    }
                    Object[] betweenValue = null;
                    if (propertyType.isArray()) {
                        betweenValue = new ArrayList<>((List<?>) value).toArray();
                    } else {
                        betweenValue = (Object[]) value;
                    }
                    Assert.state(betweenValue.length == 2, "{}参数不是范围值", fieldName);
                    wrapperMethod = queryWrapper.getClass().getMethod(condition.getName(), Object.class, Object.class, Object.class);
                    wrapperMethod.invoke(queryWrapper, colum, betweenValue[0], betweenValue[1]);
                    break;
                case IN_SQL:
                case NOT_IN_SQL:
                    wrapperMethod = queryWrapper.getClass().getMethod(condition.getName(), Object.class, String.class);
                    wrapperMethod.invoke(queryWrapper, colum, value.toString());
                    break;
                case IN:
                case NOT_IN:
                    if (!(propertyType.isArray() || Collection.class.isAssignableFrom(propertyType))) {
                        throw new RuntimeException("查询参数必须是集合或数组");
                    }
                    Class<?> paramType = propertyType.isArray() ? Object[].class : Collection.class;
                    wrapperMethod = queryWrapper.getClass().getMethod(condition.getName(), Object.class, paramType);
                    wrapperMethod.invoke(queryWrapper, colum, value.toString());
                    break;
                default:
                    wrapperMethod = queryWrapper.getClass().getMethod(condition.getName(), Object.class, Object.class);
                    wrapperMethod.invoke(queryWrapper, colum, value);
            }
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new RuntimeException(e);
        } finally {
            field.setAccessible(accessible);
        }
    }


}
